/**
 * @file chat.c
 * @brief Sorgente che racchiude le funzioni per la gestione di una semplice chat.
 *
 * Viene definita la funzione principale che serve da gestore dell'avvio della comunicazione
 * e le sottofunzioni private richiamate per gestire le varie fasi della stessa
 * comunicazione.
 *
 * @date Mar 29, 2012
 * @author Francesco Corvino
 */
#include "cutility.h"
#include "../../RscNet/rscNet.h"
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

static bool _Chat_isInitialize = false;

static char _Chat_myName[20] = "unknow";

static int _Chat_regServerSD = -1;

static DynArray _Chat_registerUser;

int Utente_cmp(const void * a, const void * b){
	return strcmp(((Utente*)a)->name,((Utente*)b)->name);
}

/**
 * @brief Inizializza i dati per la chat.
 *
 * L'inizializzazione richiede le seguenti operazioni:
 */
void _Chat_init(char *regSerAddress, char * regSerPort){

	/// - Controlla se i dati sono stati già inizializzati
	if(_Chat_isInitialize) return;

	/// - Inizializza i dati per registrare gli utenti connessi
	DynArray_init(&_Chat_registerUser,Utente_cmp,sizeof(Utente));

	/// - Specifica i suggerimenti per l'indirizzo da ottenere
	AddressInfo hints, *infoRegServer;
	memset(&hints, 0, sizeof hints); // make sure the struct is empty
	hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
	hints.ai_socktype = SOCK_DGRAM; // UDP stream sockets
	hints.ai_flags = AI_PASSIVE; // fill in my IP for me
	/// - Richiede le informazioni sul server di registrazione
	if ((getaddrinfo(regSerAddress, regSerPort,
			&hints, &infoRegServer)) != 0)  return;
	/// - Controlla se il socket era stato precedentemente aperto, nel caso viene chiuso
	if(_Chat_regServerSD != -1){
		close(_Chat_regServerSD);
	}
	/// - Prova ad aprire un socket per i vari indirizzi disponibili
	AddressInfo * res = NULL;
	for(res = infoRegServer; res != NULL; res = res->ai_next) {
		if ((_Chat_regServerSD = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
			continue;
		}
		if(connect(_Chat_regServerSD,res,sizeof(res))== -1){
			continue;
		}
		break;		// il socket è aperto è valido continuo le operazioni
	}
	/// - Controlla se non è stato possibile aprire una connessione verso un server.
	if (res == NULL) {
		_Chat_regServerSD = -1;
		return;
	}
	/// - Libera le risorse di memoria allocate
	freeaddrinfo(infoRegServer);
	/// - Aggiorna lo stato dell'inizializzazione a true.
	_Chat_isInitialize = true;
}

/**
 * @brief Attende un'input dai due file descriptor forniti.
 *
 * @param fd0 primo file descriptor.
 * @param fd1 secondo file descriptor.
 * @return restituisce il valore del file descriptor che si è sboccato o -1
 * se nessuno dei due è attivo.
 */
int _Chat_waitInput(int fd0, int fd1){
	struct timeval tv;
	tv.tv_sec = 1000; tv.tv_usec = 0;
	fd_set fdsetread;
	FD_ZERO(&fdsetread);
	FD_SET(fd0,&fdsetread);
	FD_SET(fd1, &fdsetread);
	int maxfd = (fd0>fd1)?(fd0):(fd1);
	select(maxfd+1,&fdsetread,NULL,NULL,&tv);
	if(FD_ISSET(fd0,&fdsetread)) return fd0;
	else if(FD_ISSET(fd1,&fdsetread)) return fd1;
	return -1;
}

/**
 * @brief Avvia la procedura di scambio dei messaggi con il client.
 *
 * @param sockFd specifica il socket descriptor con cui effettuare la comunicazione.
 * @param sessFd specifica il file descriptor con cui comunicare con la sessione dell'utente.
 * @param name stringa da utilizzare per identificare la comunicazione.
 * @param connInfo informazioni di rete per la connessione.
 */
void _Chat_ScambiaMessaggi(int sockFd,int sessFd,char *name, AddressInfo * connInfo){
	char msg[1000]; int sizebuff = 1000;
	do {
		memset(msg,0,sizebuff);
		int ret = _Chat_waitInput(sockFd,sessFd);
		if(ret==sockFd){
			UDP_recvMsg(sockFd,msg,sizebuff,connInfo);
			AppLib_writeToHostF("%s --> %s",name,msg);
		} else {
			AppLib_readFromHost(msg,sizebuff);
			UDP_sendMsgTo(sockFd, connInfo, msg);
		}
	} while (strncmp(msg,"end",3)!=0);
	AppLib_writeToHostF("Sessione di comunicazione %s ternimata!",name);
}

/**
 * @brief Prova ad iniviare un messaggio verso un'indirizzo.
 */
bool _Chat_TryConnection(int sockFd,AddressInfo * connInfo, char* nameChat){
	// ricava il messaggio di inizializzazione della chat
	char initMsg[255];
	sprintf(initMsg,"start %s", nameChat);
	// avvia una comunicazione
	if(!UDP_sendMsgTo(sockFd,connInfo,initMsg)){
		// il messaggio non è stato inviato..
		AppLib_writeToHostF(
				"Chat con %s non riuscita, impossibile inviare messaggi.",
				nameChat);
		return false;
	}
	return true;
}

/**
 * @brief Comando che aggiorna le configurazioni della chat.
 */
void Chat_changeConfig(){
	/// - Recupera i parametri di configurazione
	char address[255], port[20], name[20];
	int sizeAddr = AppLib_getParam("address",address, 255);
	int sizePort = AppLib_getParam("port",port, 20);
	int sizeName = AppLib_getParam("name",name,20);
	/// - Controlla la validità dei parametro name: se è stato inserito assegna il proprio nome nella chat
	if(sizeName!=0){
		strcpy(_Chat_myName,name);
	}
	/// - Controlla se l'indirizzo o la porta sono dei parametri da configurare
	if((sizeAddr>2) || (sizePort>2)) {
		_Chat_isInitialize = false;
		if(sizeAddr<2) {
			strcpy(address,_Chat_DefaultRegServerAddress);
		}
		if(sizePort<2){
			strcpy(address,_Chat_DefaultRegServerPort);
		}
		_Chat_init(address,port);
	}
	/// - Se non è stato precedentemente inizializzato o le operazioni sono fallite ritenta
	if(!_Chat_isInitialize)
		_Chat_init(_Chat_DefaultRegServerAddress,_Chat_DefaultRegServerPort);
	/// - Restituisce il feedback all'utente
	AppLib_writeToHostF("Configurazione aggiornata:\n\
			Indirizzo server di registrazione: <%s:%s>\n\
			Nome utilizzato nelle chat:<%s>",
			address,port,_Chat_myName);
}

/**
 * @brief Comando che avvia la comunicazione con il server di registrazione per ottenere gli aggiornamenti.
 *
 * L'operazione è svolta con i seguenti passi:
 */
void Chat_update(){
	/// - richiede l'inizializzazione dei dati se non è stata effettuata.
	if(!_Chat_isInitialize) _Chat_init(_Chat_DefaultRegServerAddress, _Chat_DefaultRegServerPort);
	/// - compone il messaggio di aggiornamento.
	char msgUpdate[UDP_MAX_SIZE_PKT];
	sprintf(msgUpdate, "request=register;name=%s;address=%s;port=%s;\0",
			_Chat_myName, "", "");
	/// - invia il messaggio al server
	send(_Chat_regServerSD,msgUpdate,strlen(msgUpdate),0);
	/// - attende una risposta del server
	recv(_Chat_regServerSD,msgUpdate,UDP_MAX_SIZE_PKT,0);
	/// - elabora le informazioni ottenute

}

/**
 * @brief implementa la funzione execute dell'interfaccia comando per avviare una chat.
 */
void exStartChat(){
	char address[255], port[20], name[30];
	int ret = 0;
	// recupera le informazioni sull'indirizzo con cui comunicare
	ret = AppLib_getParam("address",address, 255);
	if(ret<2) strcpy(address,"localhost");
	ret = AppLib_getParam("port",port, 20);
	if(ret<1) strcpy(port, CHAT_DEFAULT_PORT);
	ret = AppLib_getParam("name",name,30);
	if(ret<1) strcpy(name,CHAT_DEFAULT_NAME);
	// richiede un socket UDP
	AddressInfo connInfo;
	int sockFd = UDP_getFdSocketFor(address,port, &connInfo);
	if(sockFd == -1) {
		AppLib_writeToHostF(
				"Chat con %s non riuscita, errore di connessione per %s:%s",
				name, address, port);
		return;
	}
	// prova a inizializzare la connessione scambiando dei messaggi
	if(!_Chat_TryConnection(sockFd, &connInfo,name)) return;
	// ricava il filedescriptor di input con cui scambiare i messaggi.
	int sessionFd = Session_getFDRead(AppLib_getMySession());
	// avvia lo scambio messaggi
	_Chat_ScambiaMessaggi(sockFd,sessionFd,name,&connInfo);
	close(sockFd);
}
